🚀 Bài 12: DOM Nâng cao & JavaScript Event

Thao tác DOM chuyên sâu và xử lý sự kiện

← Quay lại trang chính

1. ✏️ UPDATE - Thay đổi nội dung DOM

Sau khi truy cập được phần tử DOM, chúng ta cần biết cách thay đổi nội dung của chúng để tạo ra trang web động và tương tác.

📝 textContent - Thay đổi text thuần

Mục đích: Cập nhật nội dung text an toàn, tự động escape HTML tags

Sử dụng khi: Hiển thị dữ liệu người dùng, đếm số lượng, thông báo đơn giản

textContent.js
// Hiển thị tên người dùng
const welcome = document.getElementById('welcome');
welcome.textContent = "Xin chào " + username + "!";

// Cập nhật số lượng
const counter = document.getElementById('counter');
counter.textContent = count + " sản phẩm";

🏷️ innerHTML - Thay đổi HTML bên trong

Mục đích: Cập nhật nội dung có định dạng HTML, tạo cấu trúc phức tạp

Sử dụng khi: Tạo danh sách, bài viết, card sản phẩm có định dạng

innerHTML.js
// Tạo card sản phẩm
const productCard = document.getElementById('product');
productCard.innerHTML = `
    <img src="${product.image}" alt="${product.name}">
    <h3>${product.name}</h3>
    <p class="price">${product.price} VNĐ</p>
    <button>Mua ngay</button>
`;

🎮 Thực hành: textContent vs innerHTML

Nội dung sẽ thay đổi ở đây

2. ➕ INSERT - Tạo và chèn phần tử

Để tạo nội dung động, chúng ta cần biết cách tạo mới và chèn các phần tử HTML vào trang.

🏗️ createElement() - Tạo phần tử mới

Mục đích: Tạo phần tử HTML hoàn toàn mới trong bộ nhớ

Sử dụng khi: Cần tạo nội dung động, thông báo, popup, danh sách item

createElement.js
// Tạo thông báo mới
const notification = document.createElement('div');
notification.className = 'alert alert-success';
notification.textContent = 'Đã lưu thành công!';

// Tạo item trong todo list
const todoItem = document.createElement('li');
todoItem.innerHTML = `
    <span>${taskText}</span>
    <button onclick="deleteTask()">Xóa</button>
`;

📌 appendChild() - Thêm vào cuối

Mục đích: Chèn phần tử mới vào cuối danh sách con

Sử dụng khi: Thêm tin nhắn mới, comment, sản phẩm vào danh sách

appendChild.js
// Thêm item vào danh sách
const list = document.getElementById('todoList');
const newItem = document.createElement('li');
newItem.textContent = 'Task mới';
list.appendChild(newItem); // Thêm vào cuối

// insertBefore() - Thêm vào vị trí cụ thể
const firstItem = list.firstChild;
list.insertBefore(newItem, firstItem); // Thêm vào đầu

🎮 Thực hành: Tạo và chèn phần tử

Container - các phần tử mới sẽ xuất hiện ở đây

💻 Code Examples - Tạo và Chèn phần tử

insert-demo.js
// Hàm tạo và chèn phần tử (tương tự demo trên)
function createAndInsert() {
    // 1. Lấy dữ liệu từ input
    const content = document.getElementById('elementContent').value || `Element ${itemCounter}`;
    const tag = document.getElementById('elementTag').value;
    const method = document.getElementById('insertMethod').value;
    const container = document.getElementById('insertContainer');

    // 2. Tạo phần tử mới
    const newElement = document.createElement(tag);
    newElement.textContent = content;
    
    // 3. Style cho phần tử
    newElement.style.background = '#d4edda';
    newElement.style.padding = '8px';
    newElement.style.margin = '4px 0';
    newElement.style.borderRadius = '5px';
    newElement.style.border = '1px solid #c3e6cb';

    // 4. Chèn theo phương thức được chọn
    if (method === 'appendChild') {
        container.appendChild(newElement);           // Thêm vào cuối
    } else if (method === 'prepend') {
        container.insertBefore(newElement, container.firstChild);  // Thêm vào đầu
    } else if (method === 'before') {
        container.parentNode.insertBefore(newElement, container);  // Trước container
    } else if (method === 'after') {
        container.parentNode.insertBefore(newElement, container.nextSibling); // Sau container
    }

    // 5. Cập nhật kết quả và reset input
    document.getElementById('insertResult').innerHTML = 
        `<strong>Đã tạo:</strong> <${tag}> với nội dung "${content}" bằng phương thức ${method}()`;
    document.getElementById('elementContent').value = '';
    itemCounter++;
}

// Hàm chèn HTML trực tiếp
function insertHTML() {
    const content = document.getElementById('elementContent').value || 'HTML Content';
    const container = document.getElementById('insertContainer');
    
    // insertAdjacentHTML - chèn HTML string
    const htmlContent = `<div style="background:#fff3cd; padding:8px; margin:4px 0; border-radius:5px;">
        <strong>HTML:</strong> ${content}
    </div>`;
    
    container.insertAdjacentHTML('beforeend', htmlContent);
    
    document.getElementById('insertResult').innerHTML = 
        `<strong>insertAdjacentHTML:</strong> Đã chèn HTML "${content}" trực tiếp`;
    document.getElementById('elementContent').value = '';
}

// Hàm clone phần tử cuối
function cloneLastElement() {
    const container = document.getElementById('insertContainer');
    const elements = Array.from(container.children).filter(el => el.tagName !== 'P');
    
    if (elements.length > 0) {
        const lastElement = elements[elements.length - 1];
        const cloned = lastElement.cloneNode(true);
        cloned.style.background = '#f8d7da';
        cloned.style.borderColor = '#f5c6cb';
        cloned.textContent += ' (bản sao)';
        container.appendChild(cloned);
        
        document.getElementById('insertResult').innerHTML = 
            `<strong>cloneNode:</strong> Đã sao chép phần tử cuối cùng`;
    } else {
        document.getElementById('insertResult').textContent = 'Không có phần tử nào để sao chép!';
    }
}

function clearContainer() {
    const container = document.getElementById('insertContainer');
    container.innerHTML = '

Container - các phần tử mới sẽ xuất hiện ở đây

'; itemCounter = 1; document.getElementById('insertResult').innerHTML = 'Đã xóa: Tất cả phần tử trong container'; }

3. ❌ DELETE - Xóa và thay thế phần tử

Quản lý DOM cũng bao gồm việc xóa những phần tử không còn cần thiết và thay thế nội dung cũ.

❌ remove() - Xóa phần tử

Mục đích: Xóa phần tử khỏi DOM một cách đơn giản

Sử dụng khi: Xóa thông báo, task completed, popup, modal

remove.js
// Cách hiện đại (ES6+)
const element = document.getElementById('myElement');
element.remove(); // Tự xóa chính nó

// Cách cũ (vẫn hoạt động)
const parent = element.parentNode;
parent.removeChild(element);

🔄 replaceWith() - Thay thế phần tử

Mục đích: Thay thế phần tử cũ bằng phần tử mới hoàn toàn

Sử dụng khi: Cập nhật nội dung phức tạp, thay đổi cấu trúc

replace.js
// Tạo phần tử mới
const newElement = document.createElement('div');
newElement.textContent = 'Nội dung mới';

// Thay thế
const oldElement = document.getElementById('oldElement');
oldElement.replaceWith(newElement);

🎮 Thực hành: Xóa và thay thế phần tử

📄 Item 1 - Click để chọn
📄 Item 2 - Click để chọn
📄 Item 3 - Click để chọn

👆 Click vào item để chọn, sau đó sử dụng các nút bên dưới

💻 Code Examples - Xóa và Thay thế phần tử

delete-demo.js
let selectedItem = null;
let itemCounter = 4;

// Hàm chọn item (highlight)
function selectItem(element, id) {
    // 1. Bỏ selection cũ
    document.querySelectorAll('.deletable-item').forEach(item => {
        item.style.border = '2px solid transparent';
        item.style.background = '#d4edda';
    });
    
    // 2. Highlight item được chọn
    element.style.border = '2px solid #667eea';
    element.style.background = '#e3f2fd';
    selectedItem = element;
    
    // 3. Cập nhật thông báo
    document.getElementById('deleteResult').innerHTML = 
        `<strong>Đã chọn:</strong> "${element.textContent}" - Có thể xóa hoặc thay thế`;
    
    // Update HTML source if tab is active
    if (document.getElementById('htmlTab').classList.contains('active')) {
        refreshHTMLSource();
    }
}

// Hàm xóa item được chọn
function deleteSelected() {
    if (selectedItem) {
        const text = selectedItem.textContent;
        
        // Modern way: element tự xóa chính nó
        selectedItem.remove();
        selectedItem = null;
        
        document.getElementById('deleteResult').innerHTML = 
            `<strong>remove():</strong> Đã xóa "${text}" - Phần tử tự xóa chính nó`;
        
        // Update HTML source if tab is active
        if (document.getElementById('htmlTab').classList.contains('active')) {
            refreshHTMLSource();
        }
    } else {
        document.getElementById('deleteResult').textContent = 'Vui lòng chọn một item trước khi xóa!';
    }
}

// Hàm thay thế item được chọn
function replaceSelected() {
    if (selectedItem) {
        const newContent = document.getElementById('replaceContent').value || 'Nội dung mặc định';
        const oldText = selectedItem.textContent;
        
        // 1. Tạo phần tử mới
        const newElement = document.createElement('div');
        newElement.className = 'deletable-item';
        newElement.textContent = newContent;
        
        // 2. Style cho phần tử mới
        newElement.style.background = '#d1ecf1';
        newElement.style.padding = '10px';
        newElement.style.margin = '5px 0';
        newElement.style.borderRadius = '5px';
        newElement.style.cursor = 'pointer';
        newElement.style.border = '2px solid transparent';
        
        // 3. Gắn event listener
        newElement.onclick = function() { selectItem(this, itemCounter++); };
        
        // 4. Thay thế element cũ bằng mới
        selectedItem.replaceWith(newElement);
        selectedItem = null;
        
        document.getElementById('deleteResult').innerHTML = 
            `<strong>replaceWith():</strong> "${oldText}" → "${newContent}"`;
        document.getElementById('replaceContent').value = '';
        
        // Update HTML source if tab is active
        if (document.getElementById('htmlTab').classList.contains('active')) {
            refreshHTMLSource();
        }
    } else {
        document.getElementById('deleteResult').textContent = 'Vui lòng chọn một item trước khi thay thế!';
    }
}

// Hàm thêm item mới
function addNewItem() {
    const container = document.getElementById('deleteContainer');
    
    // 1. Tạo element mới
    const newItem = document.createElement('div');
    newItem.className = 'deletable-item';
    newItem.textContent = `📄 Item ${itemCounter} - Click để chọn`;
    
    // 2. Style
    newItem.style.background = '#d4edda';
    newItem.style.padding = '10px';
    newItem.style.margin = '5px 0';
    newItem.style.borderRadius = '5px';
    newItem.style.cursor = 'pointer';
    newItem.style.border = '2px solid transparent';
    
    // 3. Event listener
    newItem.onclick = function() { selectItem(this, itemCounter); };
    
    // 4. Thêm vào container
    container.appendChild(newItem);
    
    document.getElementById('deleteResult').innerHTML = 
        `<strong>createElement + appendChild:</strong> Đã thêm Item ${itemCounter}`;
    itemCounter++;
    
    // Update HTML source if tab is active
    if (document.getElementById('htmlTab').classList.contains('active')) {
        refreshHTMLSource();
    }
}

function resetDeleteDemo() {
    const container = document.getElementById('deleteContainer');
    
    // Khôi phục HTML ban đầu
    container.innerHTML = `
        <div class="deletable-item" style="background:#d4edda; padding:10px; margin:5px 0; border-radius:5px; cursor:pointer; border:2px solid transparent;" onclick="selectItem(this, 1)">
            📄 Item 1 - Click để chọn
        </div>
        <div class="deletable-item" style="background:#d4edda; padding:10px; margin:5px 0; border-radius:5px; cursor:pointer; border:2px solid transparent;" onclick="selectItem(this, 2)">
            📄 Item 2 - Click để chọn  
        </div>
        <div class="deletable-item" style="background:#d4edda; padding:10px; margin:5px 0; border-radius:5px; cursor:pointer; border:2px solid transparent;" onclick="selectItem(this, 3)">
            📄 Item 3 - Click để chọn
        </div>
    `;
    
    selectedItem = null;
    itemCounter = 4;
    document.getElementById('deleteResult').innerHTML = 'Reset: Đã khôi phục lại demo ban đầu';
    
    // Update HTML source if tab is active
    if (document.getElementById('htmlTab').classList.contains('active')) {
        refreshHTMLSource();
    }
}
                

4. 🎉 JavaScript Events

Event (sự kiện) là các hành động xảy ra trên trang web như click chuột, nhập bàn phím, di chuyển chuột... JavaScript có thể "lắng nghe" và phản ứng với các sự kiện này.

🖱️ Các loại Event phổ biến

  • click - Khi click chuột vào phần tử
  • input - Khi người dùng nhập dữ liệu
  • mouseover/mouseout - Khi rê chuột vào/ra khỏi phần tử
  • keydown/keyup - Khi nhấn/thả phím
  • submit - Khi gửi form
  • load - Khi trang web load xong
events.js
// Cách 1: HTML attribute (không khuyến khích)
<button onclick="alert('Clicked!')">Click me</button>

// Cách 2: addEventListener (khuyến khích)
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
    console.log('Button được click!');
});

// Event với tham số
button.addEventListener('click', function(event) {
    console.log('Click tại vị trí:', event.clientX, event.clientY);
    event.preventDefault(); // Ngăn hành động mặc định
});

🎮 Thực hành: Các loại Event

🎯 Hover Event - Di chuột vào đây
🖱️ Right Click Event - Click phải vào đây

📝 Bài tập thực hành

📋 Bài 1: Todo List đơn giản

📥 Đầu vào (Input):

  • HTML cơ bản: input text, button "Thêm", ul container rỗng
  • User nhập: Tên task vào input field
  • User actions: Click "Thêm", click "Xóa", click "Hoàn thành"

📤 Đầu ra (Output):

  • Hiển thị: Danh sách task dạng <li> với nút Xóa và checkbox
  • Chức năng: Task completed có style gạch ngang + màu xám
  • Tương tác: Click Xóa → task biến mất, Click checkbox → toggle completed
  • Validation: Input rỗng → hiển thị cảnh báo, không thêm task

🔧 Kỹ thuật cần sử dụng:

createElement('li') - Tạo task mới
appendChild() - Thêm vào danh sách
addEventListener('click') - Xử lý click
element.remove() - Xóa task
classList.toggle('completed') - Đánh dấu hoàn thành
textContent - Cập nhật nội dung

📝 Bài 2: Form đăng ký với validation

📥 Đầu vào (Input):

  • HTML form: input[name, email, password, confirm], button submit
  • User actions: Gõ vào các field, click submit
  • Validation rules: Name ≥ 3 ký tự, email hợp lệ, password ≥ 6 ký tự, confirm khớp

📤 Đầu ra (Output):

  • Real-time feedback: Hiển thị lỗi ngay khi user gõ
  • Visual indicators: Field hợp lệ → border xanh, lỗi → border đỏ
  • Error messages: Div hiển thị lỗi cụ thể dưới mỗi field
  • Submit behavior: Có lỗi → preventDefault(), không lỗi → console.log data

🔧 Kỹ thuật cần sử dụng:

addEventListener('input') - Validation real-time
addEventListener('submit') - Xử lý form submit
event.preventDefault() - Ngăn submit khi có lỗi
element.style.borderColor - Thay đổi màu border
textContent - Hiển thị thông báo lỗi
RegExp - Validate email format

🖼️ Bài 3: Gallery ảnh tương tác

📥 Đầu vào (Input):

  • HTML: div container, img thumbnails (150x100px), img main display (400x300px)
  • Images array: ['image1.jpg', 'image2.jpg', 'image3.jpg', ...]
  • User actions: Click thumbnail, hover, nhấn phím ←→

📤 Đầu ra (Output):

  • Click thumbnail: Ảnh chính thay đổi src, thumbnail được highlight border
  • Hover effect: Thumbnail scale 1.1x, opacity 0.8 → 1
  • Keyboard nav: ← → chuyển ảnh, Escape đóng gallery
  • Current indicator: Hiển thị "Ảnh 2/5" dưới ảnh chính

🔧 Kỹ thuật cần sử dụng:

addEventListener('click') - Click thumbnail
element.setAttribute('src', newSrc) - Đổi ảnh
mouseover/mouseout events - Hover effects
element.style.transform - Scale effect
addEventListener('keydown') - Keyboard navigation
classList.add/remove - Highlight active thumbnail

💡 Hướng dẫn làm bài

📋 Bước thực hiện:

  1. Tạo file HTML với cấu trúc cơ bản
  2. Viết CSS cho styling và responsive
  3. Implement từng chức năng JavaScript
  4. Test và debug từng feature
  5. Optimize user experience

🎯 Tiêu chí đánh giá:

  • Functionality: Tất cả tính năng hoạt động
  • Code quality: Clean, readable, commented
  • UX/UI: Intuitive, responsive, error handling
  • DOM techniques: Sử dụng đúng methods
🚀 Bonus challenges:
Bài 1: Thêm edit task, drag & drop reorder, local storage
Bài 2: Strength meter cho password, async email check, CAPTCHA
Bài 3: Slideshow auto-play, zoom in/out, full-screen mode